package org.free.bacnet4j.util;

import java.io.*;
import java.nio.charset.Charset;

public class ByteQueue implements Cloneable {
	
	private byte queue[];
	private int head;
	private int tail;
	private int size;
	private int markHead;
	private int markTail;
	private int markSize;
	
	public ByteQueue(){
		this(1024);
	}

	public ByteQueue(int initialLength){
		head = -1;
		tail = 0;
		size = 0;
		queue = new byte[initialLength];
	}

	public ByteQueue(byte b[]){
		this(b.length);
		push(b, 0, b.length);
	}

	public ByteQueue(byte b[], int pos, int length){
		this(length);
		push(b, pos, length);
	}

	public void push(byte b){
		if(room() == 0)
			expand();
		queue[tail] = b;
		if(head == -1)
			head = 0;
		tail = (tail + 1) % queue.length;
		size++;
	}

	public void push(int i){
		push((byte)i);
	}

	public void push(long l){
		push((byte)(int)l);
	}

	public void pushU2B(int i){
		push((byte)(i >> 8));
		push((byte)i);
	}

	public void pushU3B(int i){
		push((byte)(i >> 16));
		push((byte)(i >> 8));
		push((byte)i);
	}

	public void pushS4B(int i){
		pushInt(i);
	}

	public void pushU4B(long l){
		push((byte)(int)(l >> 24));
		push((byte)(int)(l >> 16));
		push((byte)(int)(l >> 8));
		push((byte)(int)l);
	}

	public void pushChar(char c){
		push((byte)(c >> 8));
		push((byte)c);
	}

	public void pushDouble(double d){
		pushLong(Double.doubleToLongBits(d));
	}

	public void pushFloat(float f){
		pushInt(Float.floatToIntBits(f));
	}

	public void pushInt(int i){
		push((byte)(i >> 24));
		push((byte)(i >> 16));
		push((byte)(i >> 8));
		push((byte)i);
	}

	public void pushLong(long l){
		push((byte)(int)(l >> 56));
		push((byte)(int)(l >> 48));
		push((byte)(int)(l >> 40));
		push((byte)(int)(l >> 32));
		push((byte)(int)(l >> 24));
		push((byte)(int)(l >> 16));
		push((byte)(int)(l >> 8));
		push((byte)(int)l);
	}

	public void pushShort(short s){
		push((byte)(s >> 8));
		push((byte)s);
	}

	public void read(InputStream in, int length) throws IOException{
		if(length == 0)
			return;
		for(; room() < length; expand());
		int tailLength = queue.length - tail;
		if(tailLength > length)
			readImpl(in, tail, length);
		else
			readImpl(in, tail, tailLength);
		if(length > tailLength)
			readImpl(in, 0, length - tailLength);
		if(head == -1)
			head = 0;
		tail = (tail + length) % queue.length;
		size += length;
	}

	private void readImpl(InputStream in, int offset, int length) throws IOException{
		int readcount;
		for(; length > 0; length -= readcount){
			readcount = in.read(queue, offset, length);
			offset += readcount;
		}
	}

	public void push(byte b[]){
		push(b, 0, b.length);
	}

	public void push(byte b[], int pos, int length){
		if(length == 0)
			return;
		for(; room() < length; expand());
		int tailLength = queue.length - tail;
		if(tailLength > length)
			System.arraycopy(b, pos, queue, tail, length);
		else
			System.arraycopy(b, pos, queue, tail, tailLength);
		if(length > tailLength)
			System.arraycopy(b, tailLength + pos, queue, 0, length - tailLength);
		if(head == -1)
			head = 0;
		tail = (tail + length) % queue.length;
		size += length;
	}

	public void push(ByteQueue source){
		if(source.size == 0)
			return;
		if(source == this)
			source = (ByteQueue)clone();
		int firstCopyLen = source.queue.length - source.head;
		if(source.size < firstCopyLen)
			firstCopyLen = source.size;
		push(source.queue, source.head, firstCopyLen);
		if(firstCopyLen < source.size)
			push(source.queue, 0, source.tail);
	}

	public void mark(){
		markHead = head;
		markTail = tail;
		markSize = size;
	}

	public void reset(){
		head = markHead;
		tail = markTail;
		size = markSize;
	}

	public byte pop(){
		byte retval = queue[head];
		if(size == 1){
			head = -1;
			tail = 0;
		} else{
			head = (head + 1) % queue.length;
		}
		size--;
		return retval;
	}

	public int popU1B(){
		return pop() & 0xff;
	}

	public int popU2B(){
		return (pop() & 0xff) << 8 | pop() & 0xff;
	}

	public int popU3B(){
		return (pop() & 0xff) << 16 | (pop() & 0xff) << 8 | pop() & 0xff;
	}

	public int popS4B(){
		return (pop() & 0xff) << 24 | (pop() & 0xff) << 16 | (pop() & 0xff) << 8 | pop() & 0xff;
	}

	public long popU4B(){
		return (long)(pop() & 0xff) << 24 | (long)(pop() & 0xff) << 16 | (long)(pop() & 0xff) << 8 | (long)(pop() & 0xff);
	}

	public int pop(byte buf[]){
		return pop(buf, 0, buf.length);
	}

	public int pop(byte buf[], int pos, int length){
		length = peek(buf, pos, length);
		size -= length;
		if(size == 0){
			head = -1;
			tail = 0;
		} else{
			head = (head + length) % queue.length;
		}
		return length;
	}

	public int pop(int length){
		if(length == 0)
			return 0;
		if(size == 0)
			throw new ArrayIndexOutOfBoundsException(-1);
		if(length > size)
			length = size;
		size -= length;
		if(size == 0){
			head = -1;
			tail = 0;
		} else{
			head = (head + length) % queue.length;
		}
		return length;
	}

	public String popString(int length, Charset charset){
		byte b[] = new byte[length];
		pop(b);
		return new String(b, charset);
	}

	public byte[] popAll(){
		byte data[] = new byte[size];
		pop(data, 0, data.length);
		return data;
	}

	public void write(OutputStream out) throws IOException {
		write(out, size);
	}

	public void write(OutputStream out, int length) throws IOException {
		if(length == 0)
			return;
		if(size == 0)
			throw new ArrayIndexOutOfBoundsException(-1);
		if(length > size)
			length = size;
		int firstCopyLen = queue.length - head;
		if(length < firstCopyLen)
			firstCopyLen = length;
		out.write(queue, head, firstCopyLen);
		if(firstCopyLen < length)
			out.write(queue, 0, length - firstCopyLen);
		size -= length;
		if(size == 0){
			head = -1;
			tail = 0;
		} else{
			head = (head + length) % queue.length;
		}
	}

	public byte tailPop(){
		if(size == 0)
			throw new ArrayIndexOutOfBoundsException(-1);
		tail = ((tail + queue.length) - 1) % queue.length;
		byte retval = queue[tail];
		if(size == 1){
			head = -1;
			tail = 0;
		}
		size--;
		return retval;
	}

	public byte peek(int index){
		if(index >= size){
			throw new IllegalArgumentException((new StringBuilder()).append("index ").append(index).append(" is >= queue size ").append(size).toString());
		} else{
			index = (index + head) % queue.length;
			return queue[index];
		}
	}

	public byte[] peek(int index, int length){
		byte result[] = new byte[length];
		for(int i = 0; i < length; i++)
			result[i] = peek(index + i);

		return result;
	}

	public byte[] peekAll(){
		byte data[] = new byte[size];
		peek(data, 0, data.length);
		return data;
	}

	public int peek(byte buf[]){
		return peek(buf, 0, buf.length);
	}

	public int peek(byte buf[], int pos, int length){
		if(length == 0)
			return 0;
		if(size == 0)
			throw new ArrayIndexOutOfBoundsException(-1);
		if(length > size)
			length = size;
		int firstCopyLen = queue.length - head;
		if(length < firstCopyLen)
			firstCopyLen = length;
		System.arraycopy(queue, head, buf, pos, firstCopyLen);
		if(firstCopyLen < length)
			System.arraycopy(queue, 0, buf, pos + firstCopyLen, length - firstCopyLen);
		return length;
	}

	public int indexOf(byte b){
		return indexOf(b, 0);
	}

	public int indexOf(byte b, int start){
		if(start >= size)
			return -1;
		int index = (head + start) % queue.length;
		for(int i = start; i < size; i++){
			if(queue[index] == b)
				return i;
			index = (index + 1) % queue.length;
		}
		return -1;
	}

	public int indexOf(byte b[]){
		return indexOf(b, 0);
	}

	public int indexOf(byte b[], int start){
		if(b == null || b.length == 0)
			throw new IllegalArgumentException("cannot search for empty values");
		for(; (start = indexOf(b[0], start)) != -1 && start < (size - b.length) + 1; start++){
			boolean found = true;
			int i = 1;
			do{
				if(i >= b.length)
					break;
				if(peek(start + i) != b[i])
				{
					found = false;
					break;
				}
				i++;
			} while(true);
			if(found)
				return start;
		}
		return -1;
	}

	public int size(){
		return size;
	}

	public void clear(){
		size = 0;
		head = -1;
		tail = 0;
	}

	private int room(){
		return queue.length - size;
	}

	private void expand(){
		byte newb[] = new byte[queue.length * 2];
		if(head == -1){
			queue = newb;
			return;
		}
		if(tail > head){
			System.arraycopy(queue, head, newb, head, tail - head);
			queue = newb;
			return;
		} else{
			System.arraycopy(queue, head, newb, head + queue.length, queue.length - head);
			System.arraycopy(queue, 0, newb, 0, tail);
			head += queue.length;
			queue = newb;
			return;
		}
	}

	public Object clone(){
		try{
			ByteQueue clone = (ByteQueue)super.clone();
			clone.queue = (byte[])queue.clone();
			return clone;
		}
		catch(CloneNotSupportedException e){
			return null;
		}
	}

	public String toString(){
		if(size == 0)
			return "[]";
		StringBuffer sb = new StringBuffer();
		sb.append('[');
		sb.append(Integer.toHexString(peek(0) & 0xff));
		for(int i = 1; i < size; i++)
			sb.append(',').append(Integer.toHexString(peek(i) & 0xff));

		sb.append("]");
		return sb.toString();
	}

	public String dumpQueue(){
		StringBuffer sb = new StringBuffer();
		if(queue.length == 0){
			sb.append("[]");
		} else{
			sb.append('[');
			sb.append(queue[0]);
			for(int i = 1; i < queue.length; i++){
				sb.append(", ");
				sb.append(queue[i]);
			}
			sb.append("]");
		}
		sb.append(", h=").append(head).append(", t=").append(tail).append(", s=").append(size);
		return sb.toString();
	}
}